package com.se.mainView;

import com.se.clickableText.UnusualWordDetector;
import com.se.common.*;
import com.se.contextMenu.GroupContextMenu;
import com.se.contextMenu.GroupUserContextMenu;
import com.se.contextMenu.MessageContextMenu;
import com.se.utils.*;
import javafx.animation.PauseTransition;
import javafx.application.Platform;
import javafx.collections.ListChangeListener;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.geometry.Insets;
import javafx.geometry.Point2D;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.*;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.text.Text;
import javafx.scene.text.TextAlignment;
import javafx.scene.text.TextFlow;
import javafx.stage.Stage;
import javafx.stage.Window;
import javafx.util.Callback;
import javafx.util.Duration;
import org.fxmisc.richtext.CodeArea;

import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.Socket;
import java.net.URL;
import java.nio.file.Path;
import java.time.LocalDateTime;
import java.util.List;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;

import static com.se.common.Constant.ONLINE;

//屎山，敬请见证
public class MainViewController implements Initializable {
    @FXML
    public AnchorPane utilsPane;

    @FXML
    public Button btnClose;

    @FXML
    public Button btnMaximum;

    @FXML
    public Button btnMinimum;

    @FXML
    public ImageView maximumImg;

    @FXML
    public Label lblChattingUser;

    @FXML
    public ListView<User> listUserList;

    @FXML
    public ListView<ClientMessage> listMessageList;

    @FXML
    public ListView<Group> listGroupList;

    @FXML
    public Button btnSend;

    @FXML
    public VBox vBoxSendTextContainer;

    public CodeArea sendTextArea = new CodeArea();

    @FXML
    public StackPane textAreaPane;

    @FXML
    public ImageView textAreaImg;

    @FXML
    public BorderPane chatContainer;

    @FXML
    public ImageView imgBackGround;

    @FXML
    public Button btnFriendNotify;

    @FXML
    public ImageView friendNotifyImg;

    public VBox memberView;

    public StackPane memberViewPane;

    public ImageView memberViewImg = new ImageView(new Image(Objects.requireNonNull(getClass().getResource("/image/memberViewBackground.png")).toString()));

    @FXML
    public BorderPane parentBorderPane;

    @FXML
    public VBox leftVBox;

    @FXML
    public StackPane imagePane;

    @FXML
    public Button btnChooseFile;

    @FXML
    public Button btnChooseImg;

    @FXML
    public Button btnCreateGroup;

    @FXML
    public Button btnJoinGroup;

    @FXML
    public Button btnSettings;

    @FXML
    public Button btnAddFriend;

    @FXML
    public ToolBar chatToolBar;

    ListView<User> memberList = new ListView<>();

    private static MainViewController instance = null;

    public static final int MAX_IMAGE_SIZE = 400;

    @Override
    public void initialize(URL location, ResourceBundle resources) {

        listMessageList.getStylesheets().add(Objects.requireNonNull(getClass().getResource("/com/se/css/list-view.css")).toExternalForm());

        initUtilsPane();

        initSendTextArea();

        initUserList();

        initGroupList();

        initMessageList();

        initBackGround();

        initToolBar();

        initFriendNotify();
    }

    private void initUtilsPane() {
        utilsPane.getStylesheets().add(Objects.requireNonNull(getClass().getResource("/com/se/css/utils-pane.css")).toExternalForm());
        AtomicReference<Double> offsetX = new AtomicReference<>((double) 0);
        AtomicReference<Double> offsetY = new AtomicReference<>((double) 0);
        utilsPane.setOnMousePressed(event -> {
            offsetX.set(event.getSceneX());
            offsetY.set(event.getSceneY());
        });

        utilsPane.setOnMouseDragged(event -> {
            Stage stage = StageManager.get(StageType.MAIN_STAGE);
            stage.setX(event.getScreenX() - offsetX.get());
            stage.setY(event.getScreenY() - offsetY.get());
        });

        btnMinimum.setOnAction(e -> {
            if(StageManager.get(StageType.MAIN_STAGE) != null) {
                StageManager.get(StageType.MAIN_STAGE).setIconified(true);
            }
        });

        btnMaximum.setOnAction(e -> {
            Stage stage = StageManager.get(StageType.MAIN_STAGE);
            if(stage != null) {
                if(stage.isMaximized()) {
                    stage.setMaximized(false);
                    maximumImg.setImage(new Image(Objects.requireNonNull(getClass().getResource("/image/maximum-button.png")).toExternalForm()));
                }
                else {
                    stage.setMaximized(true);
                    maximumImg.setImage(new Image(Objects.requireNonNull(getClass().getResource("/image/maximum-button2.png")).toExternalForm()));
                }
            }
        });

        btnClose.setOnAction(e -> {
            if(StageManager.get(StageType.MAIN_STAGE) != null) {
                ServiceUtils.closeConnection();
                StageManager.get(StageType.MAIN_STAGE).close();
                Platform.exit();
            }
        });
    }

    public void initFriendNotify() {
        ServiceUtils.getFriendRequestFromServer();
        btnFriendNotify.setDisable(true);
        friendNotifyImg.setVisible(false);
        btnFriendNotify.getStylesheets().add(Objects.requireNonNull(getClass().getResource("/com/se/css/tool-bar.css")).toExternalForm());
        ServiceUtils.getFriendRequestList().addListener((ListChangeListener<String>) change ->
        {
            btnFriendNotify.setDisable(ServiceUtils.getFriendRequestList().isEmpty());
            friendNotifyImg.setVisible(!ServiceUtils.getFriendRequestList().isEmpty());
        });
    }

    public void initToolBar() {
        chatToolBar.getStylesheets().add(Objects.requireNonNull(getClass().getResource("/com/se/css/tool-bar.css")).toExternalForm());
        btnSettings.getStylesheets().add(Objects.requireNonNull(getClass().getResource("/com/se/css/tool-bar.css")).toExternalForm());
        btnSend.getStylesheets().add(Objects.requireNonNull(getClass().getResource("/com/se/css/send-button.css")).toExternalForm());
        btnAddFriend.getStylesheets().add(Objects.requireNonNull(getClass().getResource("/com/se/css/tool-bar.css")).toExternalForm());
        List<Button> toolBarButtons = new ArrayList<>(
                List.of(btnChooseFile, btnChooseImg, btnCreateGroup, btnJoinGroup, btnSettings, btnSend, btnAddFriend));
        for(var i : toolBarButtons) {
            i.getStyleClass().add("button");
            if(i.getTooltip() != null) {
                i.getTooltip().setShowDelay(Duration.millis(500));
            }
        }


        btnChooseFile.setOnAction(e -> new Thread(() -> {
            String path = FileChooserService.showChooseFileUI(JFileChooser.FILES_ONLY, false);
            if(path != null) {
                addClientFile(path);
            }
        }).start());

        btnChooseImg.setOnAction(e -> new Thread(() -> {
            String path = FileChooserService.showChooseFileUI(JFileChooser.FILES_ONLY, true);
            if(path != null) {
                addClientFile(path);
            }
        }).start());

        btnCreateGroup.setOnAction(e -> ServiceUtils.createGroup());
        btnJoinGroup.setOnAction(e -> ServiceUtils.joinGroup());
        btnSettings.setOnAction(e -> {
            if(StageManager.get(StageType.OPTION_STAGE) == null) {
                ClientUtils.showOptionStage();
            }
            else {
                StageManager.get(StageType.OPTION_STAGE).requestFocus();
            }
        });
    }

    public void initBackGround() {
        String path = Objects.requireNonNull(getClass().getResource("/image/background.png")).toString();
        Image image = new Image(path);
        imgBackGround.setImage(image);

        imgBackGround.fitWidthProperty().bind(parentBorderPane.widthProperty().subtract(leftVBox.widthProperty()));

        imgBackGround.fitHeightProperty().bind(parentBorderPane.heightProperty().subtract(vBoxSendTextContainer.heightProperty()).subtract(utilsPane.heightProperty()));

        imagePane.getStylesheets().add(Objects.requireNonNull(getClass().getResource("/com/se/css/image-view.css")).toExternalForm());
    }

    public MainViewController() {
        super();
        if(instance == null) {
            instance = this;
        }
    }

    public static MainViewController getInstance() {
        return instance;
    }

    public void refreshUserList() {
        listUserList.refresh();
    }

    public void refreshMemberList() {
        if(memberList != null) memberList.refresh();
    }

    public void refreshMessageList() {
        listMessageList.refresh();
    }

    @FXML
    public void btnAddFriendOnClick() {
        if(StageManager.get(StageType.ADD_FRIEND_STAGE) == null) {
            ClientUtils.showAddFriendPage();
        }
        else {
            StageManager.get(StageType.ADD_FRIEND_STAGE).requestFocus();
        }
    }

    @FXML
    public void btnFriendNotifyOnClick() {
        if(StageManager.get(StageType.FRIEND_REQUEST_STAGE) == null) {
            ClientUtils.showFriendRequestPage();
        }
        else {
            StageManager.get(StageType.FRIEND_REQUEST_STAGE).requestFocus();
        }
    }

    @FXML
    public void btnSendOnClick() {
        if(lblChattingUser.getText() == null || lblChattingUser.getText().isEmpty()) {
            ClientUtils.information_Alert("发送对象不能为空！");
            return;
        }

        if(sendTextArea.getText().isEmpty() || sendTextArea.getText() == null) {
            ClientUtils.information_Alert("发送内容不能为空！");
            return;
        }
        Message message = new Message();

        //如果当前是跟个人用户聊天
        if(ServiceUtils.getChatUser().getUserName() != null) {
            message.setReceiveUser(ServiceUtils.getChatUser().getUserName());
            message.setMsgType(MessageType.MESSAGE_COMMON);
        }
        //如果当前是和群组聊天
        else {
            message.setReceiveUser(String.valueOf(ServiceUtils.getChatGroup().getGroupId()));
            message.setMsgType(MessageType.MESSAGE_GROUP_COMMON);
        }
        message.setSendTime(LocalDateTime.now().format(ServiceUtils.formatter));
        message.setSendUser(ServiceUtils.getSpeaker().getUserName());

        //除了内容以外的基础设置全部在之前完成
        //因为一般这些是不变的
        if(!ServiceUtils.getAllFileMessage().isEmpty()) {
            sendFilesMessage(message);
            return;
        }

        message.setContent(sendTextArea.getText());

        try {
            ClientUtils.sendMessage(ServiceUtils.getConnection(
                    ConnectionType.BASE_CONNECTION).getSocket(), message);
            sendTextArea.clear();
            ServiceUtils.addMessageList(message);
        } catch (IOException e) {
            /*
            后续优化异常处理
             */
            e.printStackTrace();
        }
    }

    public void sendFilesMessage(Message message) {
        try {
            //在最开始开启文件传输线程，防止进行到一半出问题
            ServiceUtils.createFileConnection();
        } catch (Exception e) {
            /*
            异常处理待优化
             */
            e.printStackTrace();
            return;
        }
        Socket fileSocket = ServiceUtils.getConnection(ConnectionType.FILE_CONNECTION).getSocket();

        StringBuilder entireText = new StringBuilder(sendTextArea.getText());
        int offset = 0;
        int left;
        int right;
        List<FileMessage> fileList = ServiceUtils.getAllFileMessage();
        fileList.sort(Comparator.comparingInt(FileMessage::getLeftIndex));
        List<Integer> splitIndexes = new ArrayList<>();
        splitIndexes.add(0);
        for(var i : fileList) {
            //切割文字消息
            left = i.getLeftIndex() - offset;
            right = i.getRightIndex() - offset;
            entireText.delete(left, right);

            offset += right - left;

            splitIndexes.add(left);
            splitIndexes.add(right - offset);
            i.load();
            i.setFileMessage(message);
            ServiceUtils.addMessageList(i);
            try {
                ClientUtils.sendMessage(fileSocket, i);
            } catch (IOException e) {
                /*
                异常处理待优化
                 */
                e.printStackTrace();
            }
        }

        try {
            //传输结束就把文件线程关了，防止占用资源
            ServiceUtils.closeFileConnection();
        } catch (IOException e) {
            /*
            异常处理待优化
             */
            e.printStackTrace();
        }

        splitIndexes.add(entireText.length());
        for(int i = 0; i + 1 < splitIndexes.size(); i += 2) {
            //这边必须重新创建消息，因为messageList是异步更新的，可能在循环结束后调用，
            //使用相同的message会指向相同地址，更新的内容全部是最后一条
            //重新创建message对象后，即便在循环结束后调用，指向的也是不同的地址
            Message newMessage = new Message();
            newMessage.setContent(entireText.substring(
                    splitIndexes.get(i), splitIndexes.get(i + 1)).strip());

            if(newMessage.getContent() == null || newMessage.getContent().isEmpty()) {
                continue;
            }

            newMessage.setSendTime(message.getSendTime());
            newMessage.setSendUser(message.getSendUser());
            newMessage.setReceiveUser(message.getReceiveUser());
            newMessage.setMsgType(message.getMsgType());



            try {
                ClientUtils.sendMessage(ServiceUtils.getConnection(
                        ConnectionType.BASE_CONNECTION).getSocket(), newMessage);

                ServiceUtils.addMessageList(newMessage);
            } catch (Exception e) {
                /*
                后续优化异常处理
                 */
                e.printStackTrace();
            }

        }

        Platform.runLater(() -> {
            sendTextArea.clear();
            ServiceUtils.clearCurrentSpecialStyleMessages();
        });
    }

    public void initSendTextArea() {
        sendTextArea.setPrefHeight(95);
        textAreaPane.setPrefHeight(95);

        Platform.runLater(() -> textAreaImg.fitWidthProperty().bind(parentBorderPane.widthProperty().subtract(leftVBox.widthProperty())));

        Image img = new Image(Objects.requireNonNull(getClass().getResource("/image/textAreaBackground.png")).toString());
        textAreaImg.setImage(img);
        vBoxSendTextContainer.getChildren().add(2, sendTextArea);
        sendTextArea.getStyleClass().add("code-area");
        sendTextArea.getStyleClass().add("highlight");

        String cssPath = Objects.requireNonNull(getClass()
                .getResource("/com/se/css/code-area.css")).toExternalForm();
        sendTextArea.getStylesheets().add(cssPath);
        Tooltip tooltip = new Tooltip();
        tooltip.setStyle("-fx-max-width: infinity; -fx-max-height: infinity;");
        //延迟提升用户体验，防止预览窗乱跳
        PauseTransition hoverDelay = new PauseTransition(Duration.millis(500));
        //外置记录，考虑延迟中间用户改变选择项的情况
        AtomicReference<String> currentImagePath = new AtomicReference<>();

        //设置图片预览效果
        sendTextArea.setOnMouseMoved(event -> {
            if(sendTextArea.getText() == null || sendTextArea.getText().isEmpty()) {
                return;
            }

            int hoverIndex = sendTextArea.hit(event.getX(), event.getY()).getInsertionIndex();

            String imagePath = ServiceUtils.imageIsHovered(hoverIndex);

            //如果没有悬停在图片上，立即结束
            if(imagePath == null) {
                hoverDelay.stop();
                tooltip.hide();
                currentImagePath.set(null);
                return;
            }

            // 如果悬停在相同路径的图片上，启动延迟
            if(imagePath.equals(currentImagePath.get())) {

                hoverDelay.playFromStart();
            }
            else {
                currentImagePath.set(imagePath);
                hoverDelay.playFromStart();
            }

            hoverDelay.setOnFinished(e -> {

                int currentHoverIndex = sendTextArea.hit(event.getX(), event.getY()).getInsertionIndex();

                String currentPath = ServiceUtils.imageIsHovered(currentHoverIndex);

                //延迟结束后检查此时图片是否与之前图片一致
                if(currentPath != null && currentPath.equals(currentImagePath.get())) {
                    if(!tooltip.isShowing()) {
                        Image image = new Image("file:/" + currentPath);

                        ImageView imagePreview = new ImageView(image);
                        imagePreview.setPreserveRatio(true);

                        if(image.getHeight() > MAX_IMAGE_SIZE || image.getWidth() > MAX_IMAGE_SIZE) {
                            if (image.getWidth() >= image.getHeight()) {
                                imagePreview.setFitWidth(MAX_IMAGE_SIZE); // 宽图按宽度缩放
                            } else {
                                imagePreview.setFitHeight(MAX_IMAGE_SIZE); // 高图按高度缩放
                            }
                        }
                        else {
                            imagePreview.setFitHeight(image.getHeight());
                            imagePreview.setFitWidth(image.getWidth());
                        }

                        if(image.isError()) {
                            tooltip.setText("图片不存在！");
                        }
                        else {
                            //这里重新设置文本，防止用户真的直接把图片加回来了
                            tooltip.setText("");
                        }

                        tooltip.setGraphic(imagePreview);
                        tooltip.show(sendTextArea, 500, 500);
                        tooltip.hide();
                        Window window = sendTextArea.getScene().getWindow();
                        Point2D screenPos = sendTextArea.localToScreen(0, 0);
                        double tooltipX = screenPos.getX() + sendTextArea.getWidth() / 2 - tooltip.getWidth() / 2;
                        double tooltipY = screenPos.getY() - tooltip.getHeight();
                        tooltip.show(window, tooltipX, tooltipY);
                    }
                }
                else {
                    //如果此时图片与外置记录不同，就更新外置记录，重新开始延迟
                    currentImagePath.set(currentPath);
                }
            });

            // 鼠标移出区域时隐藏 Tooltip
            sendTextArea.setOnMouseExited(e -> {
                hoverDelay.stop();
                tooltip.hide();
                currentImagePath.set(null);
            });
        });

        //因为后文用了thread所以此处设置锁
        AtomicBoolean isDeleting = new AtomicBoolean(false);

        //以下代码是报错重灾区，慎改
        //设置文本框中的文件对应的文本（以下简称文件文本）与文件的同步和原子化删除
        sendTextArea.lengthProperty().addListener((obs, oldLength, newLength) -> {
            if(!ServiceUtils.doSpecialStyleTextExist() || isDeleting.get()) {
                return;
            }
            int caretPosition = sendTextArea.getCaretPosition();
            //移动所有需要移动索引的文件
            ServiceUtils.shiftSpecialStyleMessageIndex(caretPosition, newLength - oldLength);
            System.out.println("光标位置: " + caretPosition);
            IndexRange indexRange = ServiceUtils.isSpecialStyleMessageShouldBeRemoved(caretPosition);
            //监听当前删除事件并删除文件
            if(newLength < oldLength) {

                if(indexRange != null) {

                    isDeleting.set(true);
                    ServiceUtils.rmSpecialStyleMessage(caretPosition);
                    System.out.println(indexRange.getStart() + "," + indexRange.getEnd());

                    //开启新线程删除文字，防止阻塞删除文字后的UI更新
                    //如果此处不开新线程更新，则会导致GenericStyleArea中的Replace函数报错
                    //IndexOutOfRange，我猜测是因为按下backspace后自动调用该函数重新渲染文本
                    //但是进程被我阻塞，等到该函数调用时文本已经被删除，故IndexOutOfRange
                    new Thread(() -> {
                        Platform.runLater(() -> {
                            //防止出现乱七八糟的错，一旦出问题直接全部清除
                            // 你永远不知道你的用户能给你整出什么活
                            try {
                                sendTextArea
                                        .deleteText(indexRange.getStart(), indexRange.getEnd());
                            } catch (Exception e) {
                                sendTextArea.clear();
                                ServiceUtils.clearCurrentSpecialStyleMessages();
                            }
                        });
                        isDeleting.set(false);
                    }).start();

                }
            }
            else {
                if(indexRange != null && ServiceUtils.isInsertInSpecialStyleMessage(caretPosition) != null) {
                    //本来想要实现在文件中间添加就撤回的，但是太难了，摆烂了，下面的代码不会产生任何效果
                    //假装用户可能在文件中间添加文字这种事不存在吧
                    Platform.runLater(()-> sendTextArea.undo());
                }
            }
        });



        //如果用户包括文件在内的所有内容删除，则也要同步删除文件
        //疑似存在问题，待测试
        sendTextArea.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
            if(event.getCode() == KeyCode.DELETE || event.getCode() == KeyCode.BACK_SPACE) {
                int start = sendTextArea.getSelection().getStart();
                int end = sendTextArea.getSelection().getEnd();

                ServiceUtils.deleteSpecialStyleMessageIfNeeded(start, end);
            }

            if (event.isControlDown() && event.getCode() == KeyCode.V) {
                //此处剪切板来自java.awt，与javaFX中的不同，特此区分
                java.awt.datatransfer.Clipboard clipboard = Toolkit
                        .getDefaultToolkit().getSystemClipboard();
                Transferable transferable = clipboard.getContents(null);
                if (transferable == null) {
                    return;
                }


                if (transferable.isDataFlavorSupported(DataFlavor.imageFlavor)) {
                    event.consume();

                    new Thread(() -> {
                        try {
                            BufferedImage image = (BufferedImage) clipboard
                                    .getData(DataFlavor.imageFlavor);

                            Path path = Path.of(ServiceUtils.FILE_STORAGE_PATH);
                            path = path.resolve(LocalDateTime.now().format(ServiceUtils
                                    .formatter).replace(":", "-") + " image.png");

                            File outFile = new File(path.toString());
                            ImageIO.write(image, "png", outFile);

                            addClientFile(path.toString());
                        } catch (Exception e) {
                                /*
                                后续优化异常处理（可能不需要?
                                 */
                            e.printStackTrace();
                        }
                    }).start();

                } else if (transferable.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
                    event.consume();
                    new Thread(() -> {
                        try {
                            List<File> files = (List<File>) transferable
                                    .getTransferData(DataFlavor.javaFileListFlavor);
                            for (var i : files) {
                                addClientFile(i.getPath());
                            }
                        } catch (Exception e) {
                                /*
                                后续优化异常处理（可能不需要?
                                 */
                            e.printStackTrace();
                        }
                    }).start();
                }
            }
        });

        //嗨嗨嗨，没想到吧，我直接禁用了用户选中文件文本，现在不需要考虑用户在文件文本内添加东西会发生什么了
        // 早知道就这么干了，前面的很多工作都可以省略。唉，还是发现的太晚了
        sendTextArea.caretPositionProperty().addListener((obs, oldPos, newPos) -> {

            if(!ServiceUtils.doSpecialStyleTextExist()) {
                return;
            }

            IndexRange range = ServiceUtils.isInsertInSpecialStyleMessage(newPos);

            if(range != null) {
                Platform.runLater(() -> {
                    //同上，防止出现乱七八糟的错
                    try {
                        if (newPos > oldPos) {
                            sendTextArea.selectRange(range.getEnd() + 1, range.getEnd() + 1);
                        } else {
                            sendTextArea.selectRange(range.getStart() - 1, range.getStart() - 1);
                        }
                    } catch (Exception e) {
                        sendTextArea.clear();
                        ServiceUtils.clearCurrentSpecialStyleMessages();
                    }
                });
            }
        });
    }

    public void initUserList() {
        ServiceUtils.getUserListFromServer();
        listUserList.getStylesheets().add(Objects.requireNonNull(getClass().getResource("/com/se/css/chat-object-list-view.css")).toExternalForm());
        listUserList.setItems(ServiceUtils.getUserList());
        listUserList.setCellFactory(cell -> new UserCell());
        listUserList.getSelectionModel().selectedItemProperty().addListener((obs, oldVal, newVal) -> {

            if(newVal != null) {

                String id = ServiceUtils.currentChatUpdatingId();
                if(id != null) {
                    ServiceUtils.getCurrentUpdating(id).interrupt();
                }
                ServiceUtils.clearUpdatingMessage();
                sendTextArea.clear();
                ServiceUtils.clearCurrentSpecialStyleMessages();

                listGroupList.getSelectionModel().clearSelection();
                ServiceUtils.setChatUser(newVal);
                lblChattingUser.setText(newVal.getUserName());
                ServiceUtils.setNotify(newVal.getUserName(), false);
                ServiceUtils.clearMessageList();
                ServiceUtils.getMessageListFromServer();
                updateRightPanel(false);
                listMessageList.refresh();
            }
        });
    }

    public void initGroupList() {
        ServiceUtils.getGroupListFromServer();
        listGroupList.getStylesheets().add(Objects.requireNonNull(getClass().getResource("/com/se/css/chat-object-list-view.css")).toExternalForm());
        listGroupList.setItems(ServiceUtils.getGroupList());
        listGroupList.setCellFactory(cell -> new GroupCell());
        listGroupList.getSelectionModel().selectedItemProperty().addListener((obs, oldVal, newVal) -> {

            if(newVal != null) {
                String id = ServiceUtils.currentChatUpdatingId();
                if(id != null) {
                    ServiceUtils.getCurrentUpdating(id).interrupt();
                }
                listUserList.getSelectionModel().clearSelection();
                sendTextArea.clear();
                ServiceUtils.clearCurrentSpecialStyleMessages();

                ServiceUtils.clearUpdatingMessage();
                ServiceUtils.setChatGroup(newVal);
                lblChattingUser.setText(newVal.getGroupName());
                ServiceUtils.setGroupNotify(newVal.getGroupId(), false);
                ServiceUtils.clearMessageList();
                ServiceUtils.getMessageListFromServer();
                updateRightPanel(true);
            }
        });
    }

    public void initMessageList() {
        listMessageList.setItems(ServiceUtils.getMessageList());
        listMessageList.setCellFactory(new MessageCell());

        ServiceUtils.getMessageList().addListener((ListChangeListener<ClientMessage>) change -> {
            while (change.next()) {
                if (change.wasAdded()) {
                    scrollToBottom(listMessageList);
                }
            }
        });
    }

    public void updateRightPanel(boolean isGroup) {
        if(isGroup) {
            if(memberList != null) {
                memberList.getItems().clear();
            }
            createMemberListView();

            if(memberViewPane == null) {
                memberViewPane = new StackPane();
                memberViewPane.setPrefWidth(120);
                memberViewImg.setFitWidth(120);
                memberViewImg.fitHeightProperty().bind(imgBackGround.fitHeightProperty());
                memberViewPane.getChildren().add(memberViewImg);
                memberViewPane.getChildren().add(memberView);
            }
            else {
                memberViewPane.getChildren().clear();
                memberViewPane.getChildren().addAll(memberViewImg, memberView);
            }
            imgBackGround.fitWidthProperty().bind(parentBorderPane.widthProperty().subtract(leftVBox.widthProperty()).subtract(memberViewPane.widthProperty()));
            chatContainer.setRight(memberViewPane);
        }
        else {
            memberList.getItems().clear();
            imgBackGround.fitWidthProperty().bind(parentBorderPane.widthProperty().subtract(leftVBox.widthProperty()));
            chatContainer.setRight(null);
        }
    }

    public void addMemberList(User user) {
        Platform.runLater(() -> memberList.getItems().add(user));
    }

    public void createMemberListView() {
        memberView = new VBox();
        memberView.getStylesheets().add(Objects.requireNonNull(getClass().getResource("/com/se/css/member-view.css")).toExternalForm());
        memberView.setPrefWidth(120);

        memberView.setMaxHeight(-1);

        Label title = new Label("群组成员");
        String groupAI = ServiceUtils.getChatGroup().getGroupAI();
        if(groupAI != null && !groupAI.isEmpty()) {
            String aiName = groupAI.split(" ")[1];
            AIUser aiUser = new AIUser();
            aiUser.setAI_name(aiName);
            memberList.getItems().add(aiUser);
        }

        ServiceUtils.getCurrentGroupMemberListFromServer();

        memberList.setCellFactory(cell -> new GroupMemberCell());
        memberList.setMaxHeight(-1);
        memberView.getChildren().addAll(title, memberList);
    }

    public void addAIUser(AIUser aiUser) {
        memberList.getItems().addFirst(aiUser);
    }

    public void scrollToBottom(ListView<ClientMessage> listView) {
        Platform.runLater(() -> {
            int lastIndex = listView.getItems().size() - 1;
            if(lastIndex >= 0) {
                listView.scrollTo(lastIndex);
            }
        });
    }

    public void refreshGroupList() {
        listGroupList.refresh();
    }

    public static class UserCell extends ListCell<User> {
        @Override
        public void updateItem(User item, boolean empty) {
            super.updateItem(item, empty);
            Platform.runLater(() -> {
                if(item == null) {
                    setGraphic(null);
                    return;
                }
                HBox hBox = new HBox(5);
                ImageView userStatus;
                String path;

                if(item.isNotified()) {
                    path = Objects.requireNonNull(getClass().
                            getResource("/image/message.png")).toString();
                }
                else {
                    if (ONLINE.equals(item.getStatus())) {
                        path = Objects.requireNonNull(getClass().
                                getResource("/image/online.png")).toString();
                    } else {
                        path = Objects.requireNonNull(getClass().
                                getResource("/image/offline.png")).toString();
                    }
                }

                userStatus = new ImageView(new Image(path));
                userStatus.setFitHeight(20);
                userStatus.setFitWidth(20);
                Label label = new Label(item.getUserName());
                hBox.getChildren().addAll(userStatus, label);
                setGraphic(hBox);
            });
        }
    }

    public static class GroupMemberCell extends ListCell<User> {
        private ContextMenu contextMenu;
        {
            setOnContextMenuRequested(e -> {
                if(!isEmpty() && contextMenu != null) {
                    contextMenu.show(this, e.getScreenX(), e.getScreenY());
                }
            });
        }

        @Override
        public void updateItem(User item, boolean empty) {
            super.updateItem(item, empty);
            Platform.runLater(() -> {
                if(item == null) {
                    setGraphic(null);
                    return;
                }
                HBox hBox = new HBox(5);
                ImageView userStatus;
                String path;

                if (ONLINE.equals(item.getStatus())) {
                    path = Objects.requireNonNull(getClass().
                            getResource("/image/online.png")).toString();
                }
                else {
                    path = Objects.requireNonNull(getClass().
                            getResource("/image/offline.png")).toString();
                }

                contextMenu = GroupUserContextMenu.createGroupUserContextMenu(item);

                userStatus = new ImageView(new Image(path));
                userStatus.setFitHeight(20);
                userStatus.setFitWidth(20);
                Label label = new Label(item.getUserName());
                label.getStyleClass().add("member-label");
                hBox.getChildren().addAll(userStatus, label);
                setGraphic(hBox);
            });
        }
    }

    public static class MessageCell implements Callback<ListView<ClientMessage>, ListCell<ClientMessage>> {
        private final MessageContextMenu messageContextMenu = new MessageContextMenu();

        @Override
        public ListCell<ClientMessage> call(ListView<ClientMessage> param) {
            return new ListCell<>() {
                private final TextFlow textFlow = new TextFlow();
                private final Text contentText = new Text();
                private final Text cursor = new Text(" ▌");
                private boolean isBound = false;
                private ContextMenu contextMenu;
                private final HBox bubbleContainer = new HBox();

                {
                    setOnContextMenuRequested(e -> {
                        if (!isEmpty() && messageContextMenu != null) {
                            contextMenu.show(this, e.getScreenX(), e.getScreenY());
                        }
                    });

                    listViewProperty().addListener((obs, oldListView, newListView) -> {
                        if (newListView != null && !isBound) {

                            textFlow.maxWidthProperty().bind(
                                    newListView.widthProperty().multiply(0.7)
                                    .subtract(40)
                            );
                            contentText.wrappingWidthProperty().bind(
                                    newListView.widthProperty().multiply(0.7)
                                            .subtract(40)
                            );
                            isBound = true;
                        }
                    });

                    textFlow.getStylesheets().add(getClass().getResource("/com/se/css/bubble.css").toExternalForm());
                    textFlow.getStyleClass().add("chat-bubble");
                    textFlow.setPadding(new Insets(5));
                    textFlow.setLineSpacing(3);
                    textFlow.setMaxWidth(Region.USE_PREF_SIZE);
                    textFlow.setMaxWidth(Region.USE_PREF_SIZE);

                    bubbleContainer.setMaxWidth(Region.USE_PREF_SIZE);
                    bubbleContainer.getChildren().add(textFlow);
                }

                @Override
                public void updateItem(ClientMessage item, boolean empty) {
                    super.updateItem(item, empty);
                    if (item == null) {
                        setGraphic(null);
                        return;
                    }

                    contextMenu = messageContextMenu.createMessageContextMenu(item);

                    textFlow.getChildren().clear();
                    textFlow.setTextAlignment(TextAlignment.CENTER);
                    textFlow.getStyleClass().removeAll("other-bubble", "user-bubble");

                    VBox cellBox = new VBox();
                    Label lblSpeaker = new Label();
                    lblSpeaker.getStyleClass().add("message-label");
                    cellBox.setAlignment(Pos.CENTER);

                    bubbleContainer.setAlignment(Pos.CENTER);
                    bubbleContainer.setPadding(new Insets(5));

                    contentText.textProperty().unbind();
                    contentText.textProperty().bind(item.getContentProperty());

                    cursor.setFill(Color.GRAY);
                    cursor.visibleProperty().bind(item.getAIProperty().and(item.isCompleteProperty()));


                    textFlow.heightProperty().addListener((obs, oldH, newH) ->
                            setPrefHeight(newH.doubleValue() + 10));

                    if (item.getSendUser().equals(ServiceUtils.getSpeaker().getUserName())) {
                        bubbleContainer.setAlignment(Pos.CENTER_RIGHT);

                        textFlow.getStyleClass().add("user-bubble");
                        textFlow.setTextAlignment(TextAlignment.RIGHT);

                        cellBox.setAlignment(Pos.CENTER_RIGHT);
                        lblSpeaker.setText("[" + item.getSendTime() + "] " + item.getSendUser());
                    } else {
                        bubbleContainer.setAlignment(Pos.CENTER_LEFT);

                        textFlow.getStyleClass().add("other-bubble");
                        textFlow.setTextAlignment(TextAlignment.LEFT);

                        cellBox.setAlignment(Pos.CENTER_LEFT);
                        lblSpeaker.setText(item.getSendUser() + " [" + item.getSendTime() + "]");
                    }

                    //如果该消息为AI且当前存在待更新的AI消息
                    if(item.isAI() && ServiceUtils.getCurrentAIMessage(item.getId()) != null) {
                        //确保当前更新线程唯一
                        if(ServiceUtils.getCurrentUpdating(item.getId()) == null) {
                            Thread thread = new Thread(() -> ServiceUtils.updateAIMessage(item));
                            thread.start();
                            ServiceUtils.addUpdatingList(item.getId(), thread);
                        }
                    }

                    if(MessageType.MESSAGE_SENTENCE_EXPLAIN.equals(item.getMsgType())) {
                        lblSpeaker.setText("AI小助手" + " [" + item.getSendTime() + "]");
                    }

                    if(MessageType.MESSAGE_FILE.equals(item.getMsgType())) {
                        textFlow.getChildren().add(SpecialStyleTextFactory.createFileMessageText(item));
                        setOnMouseClicked(event -> {
                            if(event.getClickCount() == 2) {
                                onDoubleClickFile(item);
                            }
                        });
                    }
                    else if(MessageType.MESSAGE_FILE_IMAGE.equals(item.getMsgType())) {
                        textFlow.getStyleClass().removeAll("other-bubble", "user-bubble");
                        textFlow.getChildren().add(SpecialStyleTextFactory.createImageMessage(item));
                        setOnMouseClicked(event -> {
                            if(event.getClickCount() == 2) {
                                onDoubleClickImage(item);
                            }
                        });
                    }
                    else if(MessageType.MESSAGE_SENTENCE_EXPLAIN.equals(item.getMsgType())
                            || ServiceUtils.getMessageList().indexOf(item) //仅对最近的消息进行分词服务
                            < ServiceUtils.getMessageList().size() - ServiceUtils.MAX_DETECT_NUM //因为该操作开销很大
                    ) {
                        textFlow.getChildren().addAll(contentText);
                    }
                    else {
                        for(var i : UnusualWordDetector.createUnusualRichText(item.getContent())) {
                            textFlow.getChildren().add(i);
                        }
                    }

                    cellBox.getChildren().addAll(lblSpeaker, bubbleContainer);
                    setGraphic(cellBox);
                }
            };
        }
    }

    public static class GroupCell extends ListCell<Group> {
        private ContextMenu contextMenu;

        {
            setOnContextMenuRequested(e -> {
                if(!isEmpty() && contextMenu != null) {
                    contextMenu.show(this, e.getScreenX(), e.getScreenY());
                }
            });
        }

        @Override
        public void updateItem(Group item, boolean empty) {
            super.updateItem(item, empty);
            Platform.runLater(() -> {
                if(item == null || empty) {
                    setGraphic(null);
                    return;
                }

                contextMenu = GroupContextMenu.createGroupContextMenu(item);
                String path;
                HBox hBox = new HBox(5);
                if(item.isNotify()) {
                    path = Objects.requireNonNull(getClass().
                            getResource("/image/message.png")).toString();

                }
                else {
                    path = Objects.requireNonNull(getClass()
                            .getResource("/image/online.png")).toString();
                }
                ImageView image = new ImageView(new Image(path));
                image.setFitHeight(20);
                image.setFitWidth(20);
                Label label = new Label(item.getGroupName());
                hBox.getChildren().addAll(image, label);
                setGraphic(hBox);
            });
        }
    }

    //我总觉得这里是错的，但是是对的
    public void addClientFile(String path) {
        String fileName = FileChooserService.getFileName(path);
        int left;
        if(sendTextArea.getText() == null) {
            left = 0;
        }
        else {
            left = sendTextArea.getText().length() + 1;
        }
        int right = left + fileName.length() + 2;
        FileMessage fileMessage = new FileMessage(left, right, path);

        if(fileName.contains(".jpg") || fileName.contains(".png")) {
            fileMessage.setMsgType(MessageType.MESSAGE_FILE_IMAGE);
        }
        else {
            fileMessage.setMsgType(MessageType.MESSAGE_FILE);
        }

        Platform.runLater(() -> {
            sendTextArea.appendText(" ");
            sendTextArea.append("[" + fileName + "]", Collections.singleton("highlight"));
            ServiceUtils.addFileMessage(fileName, fileMessage);
            sendTextArea.appendText(" ");
        });
    }

    public void addAtMessage(String username) {
        int left;
        if(sendTextArea.getText() == null) {
            left = 0;
        }
        else {
            left = sendTextArea.getText().length() + 1;
        }

        if(ServiceUtils.getSpecialStyleMessage(username) != null) {
            ClientUtils.information_Alert("你已经@TA了！");
            return;
        }

        int right = left + username.length() + 3;
        String content = " @" + username + " ";
        AtMessage atMessage = new AtMessage(left, right, content);
        Platform.runLater(() -> {
            sendTextArea.appendText(" ");
            sendTextArea.append(content, Collections.singleton("highlight"));
            ServiceUtils.addAtMessage(username, atMessage);
            sendTextArea.appendText(" ");
        });
    }

    public static void onDoubleClickImage(ClientMessage message) {
        Path path = Path.of(message.getContent());
        String fileName = path.getFileName().toString();

        //如果图片/文件消息不是自己发的，那么就需要改变路径
        if(!message.getSendUser().equals(ServiceUtils.getSpeaker().getUserName())) {
            Path newPath = Path.of(ServiceUtils.FILE_STORAGE_PATH);
            path = newPath.resolve(fileName);
        }

        File file = new File(path.toString());
        if(!file.exists()) {
            ClientUtils.information_Alert("文件不存在！");
            return;
        }

        if(Desktop.isDesktopSupported()) {
            Desktop desktop = Desktop.getDesktop();
            if(desktop.isSupported(Desktop.Action.OPEN)) {
                try {
                    desktop.open(file);
                } catch (IOException e) {
                    /*
                    异常处理待优化
                     */
                    e.printStackTrace();
                }
            }
            else {
                ClientUtils.information_Alert("当前环境不支持直接打开文件！");
            }
        }
        else {
            ClientUtils.information_Alert("无桌面环境支持！");
        }
    }

    public static void onDoubleClickFile(ClientMessage message) {
        Path path = Path.of(message.getContent());
        String fileName = path.getFileName().toString();

        //如果图片/文件消息不是自己发的，那么就需要改变路径
        if(!message.getSendUser().equals(ServiceUtils.getSpeaker().getUserName())) {
            Path newPath = Path.of(ServiceUtils.FILE_STORAGE_PATH);
            path = newPath.resolve(fileName);
        }

        File file = new File(path.toString());
        if(!file.exists()) {
            ClientUtils.information_Alert("文件不存在！");
            return;
        }

        File parentDir = file.getParentFile();

        if(Desktop.isDesktopSupported()) {
            Desktop desktop = Desktop.getDesktop();
            if(desktop.isSupported(Desktop.Action.OPEN)) {
                try {
                    desktop.open(parentDir);
                } catch (IOException e) {
                    /*
                    异常处理待优化
                     */
                    e.printStackTrace();
                }
            }
            else {
                ClientUtils.information_Alert("当前环境不支持直接打开文件！");
            }
        }
        else {
            ClientUtils.information_Alert("无桌面环境支持！");
        }
    }

    public void chatWith(User user) {
        listUserList.getSelectionModel().select(user);
    }
}
